﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.IO;
using System.Linq;

namespace Communicator
{
    public class Server
    {
        #region Fields
        public int ServerPort { get; set; }

        protected Socket _server;

        protected bool _started = false;
        public bool Started
        {
            get { return _started; }
            set { _started = value; }
        }

        protected Dictionary<int, StateObject> _dictClient;
        public Dictionary<int, StateObject> DictClient
        {
            get { return _dictClient; }
            set { _dictClient = value; }
        }

        public int ClientCount { get { return _dictClient.Count; } }

        protected Thread _threadListening;

        protected AutoResetEvent _flagListen = new AutoResetEvent(false);

        protected Logger _log;

        protected object _lockObj = new object();
        #endregion

        #region Function Pointer
        public FpEventHandle<string> FpClientConnected { get; set; }
        public FpEventHandle<string> FpClientDisconnected { get; set; }
        public FpEventHandle<string> FpReceive { get; set; }
        public FpEventHandle<int> FpSend { get; set; }

        #endregion

        #region Constructor
        public Server(int port)
        {
            ServerPort = port;
            FpClientConnected = (x, y) => { };
            FpClientDisconnected = (x, y) => { };
            FpReceive = (x, y) => { };
            FpSend = (x, y) => { };
        }
        #endregion

        #region Start Stop
        public void Start()
        {
            _log = new Logger("Server", "Log/Communicator");
            try
            {
                _server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                _server.Bind(new IPEndPoint(IPAddress.Any, ServerPort));
                _server.Listen(100);
                _log.WriteLog("Start listening at port {0}... !", ServerPort);
                _started = true;
            }
            catch (Exception ex) { _log.WriteLog("Cannot initialize the server. ERORR: {0}", ex.StackTrace); }
            _dictClient = new Dictionary<int, StateObject>();
            _threadListening = new Thread(ListenFunc);
            _threadListening.Start();
        }

        public void Stop()
        {
            _flagListen.Close();

            try { _threadListening.Abort(); }
            catch (Exception ex) { _log.WriteLog("Cannot stop listening. Exception: {0}", ex.StackTrace); }

            try { _server.Shutdown(SocketShutdown.Both); }
            catch (Exception ex) { _log.WriteLog("Server stopped failed. Exception: {0}", ex.StackTrace); }
        }
        #endregion

        #region Callback Function
        protected void ListenFunc()
        {
            while (true)
            {
                _server.BeginAccept(new AsyncCallback(OnClientConnect), _server);
                _flagListen.WaitOne();
            }
        }

        protected void OnClientConnect(IAsyncResult result)
        {
            try
            {
                _flagListen.Set();
                Socket server = (Socket)result.AsyncState;
                Socket client = server.EndAccept(result);
                int id = !_dictClient.Any() ? 1 : _dictClient.Keys.Max() + 1;
                StateObject state = new StateObject() { Id = id, Socket = client, Name = client.RemoteEndPoint.ToString() };
                _dictClient[id] = state;
                _log.WriteLog("Client '{0}' connected !", state.Name);
                FpClientConnected(this, state.Name);
                client.BeginReceive(state.Buffer, 0, StateObject.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), state);
            }
            catch (Exception)
            {
                _log.WriteLog("Stop listening !");
            }
        }

        protected void ReceiveCallback(IAsyncResult result)
        {
            StateObject state = (StateObject)result.AsyncState;
            Monitor.Enter(_lockObj);
            try
            {
                int nReceived = state.Socket.EndReceive(result);

                if (nReceived > 0)
                {
                    byte[] buffer = state.Buffer;
                    state.AppendData(buffer, 0, nReceived);
                    string content = state.DataBuilder.ToString();
                    //if (content.IndexOf("\r\n\r\n\0") > -1)
                    {
                        //content = content.Remove(content.IndexOf("\r\n\r\n\0"));
                        FpReceive(state, content);
                        _log.WriteLog("Received from '{0}': '{1}'", state.Name, content);
                        state.ResetBuffer();
                    }
                }

                state.Socket.BeginReceive(state.Buffer, 0, StateObject.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), state);
            }
            catch (Exception)
            {
                _dictClient.Remove(state.Id);
                _log.WriteLog(string.Format("Client '{0}' disconnected !", state.Name));
                FpClientDisconnected(this, state.Name);
            }
            finally { Monitor.Exit(_lockObj); }
        }

        protected void SendCallback(IAsyncResult result)
        {
            StateObject state = (StateObject)result.AsyncState;
            Monitor.Enter(_lockObj);
            try
            {
                int len = state.Socket.EndSend(result);
                _log.WriteLog("Send to '{0}' (IP: {1}) successfully: '{2}'", state.Name, state.Socket.RemoteEndPoint.AddressFamily, state.LastSendData);
                if (FpSend != null) FpSend(state, len);
            }
            catch (Exception ex) { _log.WriteLog("Send to '{0}' (IP: {1}) failed. ERROR: '{2}'", state.Name, state.Socket.RemoteEndPoint.AddressFamily, ex.StackTrace); }
            finally { Monitor.Exit(_lockObj); }
        }

        #endregion

        #region Send
        /// <summary>
        /// Send message to client base on name of client
        /// No exception (see log)
        /// </summary>
        /// <param name="clientName">Client name</param>
        /// <param name="mess">Message</param>
        public void Send2User(string clientName, string mess)
        {
            byte[] buffer = Encoding.UTF8.GetBytes(mess);
            StateObject client = GetClient(clientName);
            client.LastSendData = mess;
            Monitor.Enter(_lockObj);
            try
            {
                client.Socket.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(SendCallback), client);
            }
            catch (Exception ex) { _log.WriteLog("Send to '{0}' (IP: {1}) failed. ERROR: '{2}'", client.Name, client.Socket.AddressFamily, ex.StackTrace); }
            finally { Monitor.Exit(_lockObj); }
        }

        /// <summary>
        /// Send message to all connected client.
        /// </summary>
        /// <param name="mess">Message content</param>
        public void Send(string mess)
        {
            foreach (StateObject client in _dictClient.Values)
            {
                Send2User(client.Name, mess);
            }
        }
        #endregion

        #region !@@$#%$^%&%

        public int GetId(string clientName)
        {
            foreach (var client in _dictClient.Values)
            {
                if (client.Name.ToUpper() == clientName.ToUpper())
                    return client.Id;
            }
            return -1;
        }

        public StateObject GetClient(string clientName)
        {
            int id = GetId(clientName);
            if (id != -1)
                return _dictClient[id];
            else
                return null;
        }
        #endregion
    }

    public class StateObject
    {
        public static int BufferSize = 1024 * 1024;

        public int Id { get; set; }
        public string Name { get; set; }
        public Socket Socket { get; set; }
        public byte[] Buffer { get; set; }
        public StringBuilder DataBuilder = new StringBuilder();
        public byte[] Data { get; set; }
        public int ReceivedLength { get; set; }
        public string LastSendData { get; set; }
        public string LastCompleteString { get; set; }

        public ManualResetEvent FlagReceived { get; set; }

        public StateObject()
        {
            //DataBuilder = new StringBuilder();
            Name = "";
            Buffer = new byte[BufferSize];
            ReceivedLength = 0;
            FlagReceived = new ManualResetEvent(false);
        }

        public void AppendData(byte[] buffer, int offset, int count)
        {
            DataBuilder.Append(Encoding.UTF8.GetString(buffer, offset, count));
        }

        public void ResetBuffer()
        {
            DataBuilder.Clear();
        }
    }
}
